#!/usr/bin/python
###############################################################################
#Programmers:                                                                 #
#             Danil Dotsenko, dd@accentsolution.com                           #
#Date: 07/17/2006                                                             #
#Filename: compatlayer.py                                                     #
#Version: 0.4                                                                 #
#                                                                             #
#                                                                             #
#       Copyleft 2006 Danil Dotsenko                                          #
#                                                                             #
#    This program is free software; you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation; either version 2 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program; if not, write to the Free Software              #
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA #
#                                                                             #
###############################################################################

'''
This file is a collection of functions specifically targeted at manipulating
Rainlendar resources like, INI files, images, etc
'''

class chopper:
	'''
	Rainlendar-specific functions utilizing ImageMagic for cutting
	rainlendar images into those acceptable for SuperKaramba.
	'''

	def __init__(self):
		from commands import getoutput
		import os.path
		self.commands = {'identify':'identify', 'convert':'convert'}
		self.commandsPresent = False

	def check(self, setFlag=True):
		'''
		Checking for ImageMagic's identify and convert commands.
		'''
		total = 0
		for i in self.commands.keys():
			output = getoutput('which '+i)
			if output:
				testtext = getoutput(output+' -version')
				if testtext.__contains__('ImageMagic'):
					total = total + 1
					self.commands[i]=output
					# now that entry hsould look like that commands['convert']='/usr/bin/convert'
		if total == self.commands.__len__(): # meaning we found them all and converted into full paths.
			if setFlag==True:
				self.commandsPresent = True
			return True
		else:
			return False

	def chop(self, image, parts, savepath=None, suffix=None, ):
		'''
		Chops image (given as file name) into equal parts
		based on dimensions of the image. The longer side of image is
		considered the iteration axis.
		image - image file name
		parts - how many parts are there in the image.
		savepath (not implemented yet)  - where to save new images. If none, will be same as image path.
		suffix (not implemented yet) - by default we add '_n' where n is serial number of image.

		WARNING: this function holds the flow until chopping is done.
		This will freeze operations until we are done.
		Consider implementing threading, but also expect the main code to "expect"
		the results when flow is returned to it. I.e. main code may expect something
		to exist that hasn't beed chopped off in the thread yet.
		'''
		if self.commandsPresent:
			# determine where to cut - horizontally or vertically
			identify = self.commands['identify']+' -format "%w:%h" '
			convert = self.commands['convert']
			w,h = getoutput(identify+image).split(':')
			try:
				w = int(w)
				h = int(h)
			except ValueError:
				return False
			if w > h: # image iterations are arranged along horizontal axis
				w = w / parts # now, w is width of individual part.
				divider = str(w)+'x'
			else: # image iterations are arranged along vertical axis
				h = h / parts # now, w is width of individual part.
				divider = 'x'+str(h)
			# lets disassemble the image name.
			filename, fileext = os.path.splitext(image)
			command = convert+' '+image+' -crop '+divider+' '+filename+'"_%d"'+fileext
			output = getoutput(command)
			if output: # outup exists in case of errors.
				return False
			else:
				return True


###################################################################
# Custom ini Parser
# This one is particulary designed for miloCalendar / Rainlendar usage.

'''
This is a compatibility layer for reading new and old-style (pre-0.22)
 Rainlendar ini files.

 There are 2 functions here:
 sectionOrder - that reads the file directly and determines the drawing order.
 sectionGet - returns "cleaned up" options for a given "logical" section.

 See respective functions' notes for more detail.
'''

def sectionOrder(fileName):
	'''
	sectionOrder(fileName):

	Perhaps the most important function for Rainlendar ini file parser.
	This function will collect names of all sections in the order they are
	mentioned and will output them as dictionary.
	The dictionary contains the logical section as key and actual as value.
	Example: Background:Rainlendar, Days:Rainlendar, Today:Rainlendar for pre .22 style ini.
	'''

	filePtr = open(fileName)

	sectionDict = {} # note. this is a dictionary of key=value pairs.
	# It contains the "normalized" string as key and "localized" as value.
	# example {'Background':'Rainlendar', 'Days':'Rainlendar', 'Today':'Rainlendar'}
	renderOrder = [] # apparently dictionary type does not keep the order. So, we have to use this

	import re
	srchMask = {}
	# these masks will scan for [section name] and section+optionname=value
	# schOptPart will then extract section name if it is burried in the option name.
	srchMask['sectionSignature'] = re.compile('(?<=^\[).+(?=\]\s*$)')
	srchMask['optionSignature'] = re.compile('(?<=^)\w+=.+(?=\s*$)')
	sections = ['Background', 'DaysWeekend', 'Days', 'Today', 'Weekdays', 'Month', 'Year', 'WeekNumbers', 'EventList', 'Event', 'ToolTip', 'Todo', 'MessageBox']
	# note, there are other sections, namely [Button X] we will specifically avoid scanning those.
	# instead we will store their name and will use COnfigParser on them to get supported values.
	# Note 'DaysWeekend' is used in pre-0.22 ini, while 'Weekdays' becomes a section later.
	srchMask['sectionNameFilter'] = re.compile('|'.join(sections)) # you get something like 'asdf|zxcv|qwer' as search criteria

	line = filePtr.readline()
	specialCase = None
	while line:
		if specialCase:
			# It appears we are in Rainlendar or Days section.
			# That section exists only in old-style INI format, where Rainlendar section would hold all options.
			# Since no section names exist in that format, we have to parse the section names out of the option names.
			if srchMask['optionSignature'].search(line): # this checks for "anytext=anything" in the beginning of the line.
				option = srchMask['optionSignature'].search(line).group(0)
				if srchMask['sectionNameFilter'].match(option): # matching against a list of known sections that can appear without section header.
					section = srchMask['sectionNameFilter'].match(option).group(0)
					if not sectionDict.__contains__(section):
						sectionDict[section]=specialCase
						renderOrder.append(section)
		if srchMask['sectionSignature'].search(line): # found "[anytext]" in the beginning of the line.
			if specialCase:
				# Yess! we are out of that dreadfull Rainlendar section.
				specialCase = None
			section = srchMask['sectionSignature'].search(line).group(0)
			if section == 'Rainlendar':
				# ok, lets scrub option names for sections.
				specialCase = 'Rainlendar' # means - process all lines until next section shows up.
				# note!!! we do not add 'Rainlendar' to the section list.
				# instead, it will be a value for all quasi-sections going into keys.
			elif section == 'Days':
				specialCase = 'Days'
				# for fricken's sake! stop nesting things inside of sections!
				# now we have to search for DaysWeekend quasi-section.
				sectionDict[section]=section
				renderOrder.append(section)
			else:
				sectionDict[section]=section
				renderOrder.append(section)
		line = filePtr.readline()
	filePtr.close()

	# REMOVING UNSUPPORTED SECTIONS!!!!!!!!!!!!
	# now... lets get our scisors and snip snip the parts we can't handle yet.
	# lets hope there will be less of these in the future.
	# commenting out things here also leaves less work for other
	sections = ['DaysWeekend', 'Weekdays', 'WeekNumbers', 'ToolTip', 'Todo', 'MessageBox', 'EventList', 'Button.*', 'Image.*', 'Time.*', 'Profile.*']
	srchMask['cantHandleYet'] = re.compile('|'.join(sections))
	i = renderOrder.__len__()-1
	while i > -1:
		if srchMask['cantHandleYet'].match(renderOrder[i]):
			renderOrder.__delitem__(i)
		i = i - 1

	# I wander if this will free up some memory...
	srchMask = None
	return renderOrder, sectionDict


def sectionGet(section, dictionary, cfg):
	'''
	This function will return a dict object of options avaiabe in the
	pseudo-sections and non-pseudo-sections. We are using dictionary to
	translate logical section names into actual.

	sectionGet(section, dictionary, cfg): where,
	section - "logical, internal" section name
	dictionary - dict type var with pairs of logical=actual section names
					we rely on ini-parsing algorythm to set that up correctly
	cfg - configuration object containing the target INI file.

	This function returns the dict object containing all present settings
	RELEVANT to the reqested section. We do some special-case collection and
	option-name chopping to arrive to something like
	section dictionary object
	 \_ option (!short name, like "X", not "DaysX" composites!)
	     \_ value (it may be type-converted, or all strings, depending on engine)
	'''

	answer = {}
	if dictionary.__contains__(section): # generic test against errors
		optionprefix = section
		section = dictionary[section]
		# it contains reference to tha actual section name.
		# it may be the same as "seceion" or, maybe "Rainlendar" for old-style INI
		options = cfg.get(section)
		for option in options:
			# now we will be disecting option names, finding ones matching the needed section and
			# spitting out only the valuable portions
			# Example: Looking for section "Days" : DaysX = 230 > X = 230.
			if optionprefix == option[:optionprefix.__len__()]: # here we detect if option begins with Section name
				fulloptionname = option
				option = option[optionprefix.__len__():] # here we take the remainder of the optionname and make it the new option
				answer[option]=cfg.get(section, fulloptionname)

	# SPECIAL CASES:
	# - WeekdayNames option, value in Weekdays section will be *missing* due to missing "s"
	# - DaysSeparation in some other section but Days. Need to find it.
	# - EventList entries will get into Event if Rainlendar section was used
	#    these will be picked up correctly when asked separately for EventList
	# - DaysWeekend entries will get into Days section if Rainlendar section was used
	#    it will be picked up correctly when asked separately for DaysWeekend
	if optionprefix == "Weekdays":
		answer['Names'] = cfg.get(section,'WeekdayNames')
	elif optionprefix == "Event" and section == 'Rainlendar':
		options = answer.keys()
		for option in options:
			if option[:4]=='List':
				answer.__delitem__(option)
	elif optionprefix == "Days":
		options = answer.keys()
		for option in options:
			if option[:7]=='Weekend':
				answer.__delitem__(option)
	return answer

def convertCfg(fileName):
	'''
	This function is the "interface" for all INI converter functions above.
	It takes an INI file name and returns:
	- array with "section order of appearance" and
	- dict type with complete contents of settings.
	'''
	renderOrder = []
	convertedcfg = {}
	if fileName:
		# will need to insert test for presense of the file here, maybe...
		# in the meantime, will rely on code that calls this to check for file.
		renderOrder, sectionDict = sectionOrder(fileName)
		# print renderOrder, sectionDict, '\n'
		import localconf
		cfg = localconf.cfgObject(fileName)
		for section in renderOrder:
			print section
			test = sectionGet(section, sectionDict, cfg)
			print type(test), test
			convertedcfg[section] = sectionGet(section, sectionDict, cfg)
	else:
		print "FIle name was an empty string. Skipping..."
	return renderOrder, convertedcfg

# *iniParser*
###################################################################

def test():
	print convertCfg('./skins/default/default.ini')
	# propercfg.save('./skins/default/default.ini')

if __name__ == '__main__':
	test()